﻿/*    
    OpenMheg - A free MHEG-5 library
    Copyright (C) 2008 Dharmesh Malam

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.AddIn;
using System.Collections.Generic;
using System.Text;
using AddInView;
using ASN1Decoder.Builders.Nodes;
using Core.Model.Data;
using Exceptions;
using ASN1Decoder.Builders;
using Core.Model;
using Core.Model.Groups;

namespace ASN1Decoder
{
    [   AddIn("ASN1Decoder",
        Version = "0.2.0.0",
        Description = "A binary ASN1Decoder",
        Publisher = "OpenMHEG")]
    public class ASN1Parser : DecoderAddInView
    {
        System.IO.Stream stream;
        
   
        public override Object Decode(System.IO.Stream MHEGGroup)
        {
            this.stream = MHEGGroup;
    
            if (stream.Length == 0) return null;
            
            TaggedNode tree = (TaggedNode)DoParse();

            Group group = Builder.Build(tree) ;

            return group;
            
        }
           
        
        private int Position
        {
          get { return (int) stream.Position; }
        
        }
        
        /// <summary>
        /// Get the next byte. 
        /// </summary>
        /// <returns></returns>
        private int GetNextChar()
        {
            return stream.ReadByte();
        }

        /// <summary>
        /// Parse a string argument.  ASN1 strings can include nulls as valid characters.
        /// </summary>
        private string ParseString(int endStr)
        {
            // TODO: Don't deal with indefinite length at the moment.
            //Logging.Assert(endStr != INDEFINITE_LENGTH); 

            int nLength = endStr - Position;
            char[] stringValue = new char[nLength];
            int p = 0;
            while (Position < endStr) stringValue[p++] = (char)GetNextChar();
            return new string(stringValue, 0, p);
        }

        /// <summary>
        /// Parse an integer argument.  Also used for bool and enum.
        /// </summary>
        /// <param name="endInt"></param>
        /// <returns></returns>
        private int ParseInt(int endInt)
        {
            int intVal = 0;
            bool firstByte = true;
            
            // TODO: Don't deal with indefinite length at the moment.
            //Logging.Assert(endInt != INDEFINITE_LENGTH); 
            
            while (Position < endInt) 
            {
                int ch = GetNextChar();
                // Integer values are signed so if the top bit is set in the first byte
                // we need to set the sign bit.
                if (firstByte && ch >= 128) intVal = -1;
                firstByte = false;
                intVal = (intVal << 8) | ch;
            }
            return intVal;
        }


        
        private static int INDEFINITE_LENGTH = -1;
        private static int Universal = 0;
        private static int Context = 1;
        private static int Pseudo = 2;
        

        //  Simple recursive parser for ASN1 BER.
        private ASN1Node DoParse()
        {
            int ch;
            // Tag class
            int tagClass = Universal;
            // Byte count of end of this item.  Set to INDEFINITE_LENGTH if the length is Indefinite.
            int endOfItem;
            int tagNumber = 0;

            // Read the first character.
            ch = GetNextChar();

            // ASN1 Coding rules: Top two bits (0 and 1) indicate the tag class.
            // 0x00 - Universal,  0x40 - Application, 0x80 - Context-specific, 0xC0 - Private
            // We only use Universal and Context.
            switch (ch & 0xC0) 
            {
            case 0x00: // Universal
                    tagClass = Universal;
                break;
            case 0x80:
                tagClass = Context;
                break;
            default:
                throw new OpenMhegException("Invalid tag class = " + ch);
            }

            // Bit 2 indicates whether it is a simple or compound type.  Not used.
            // Lower bits are the tag number.
            tagNumber = ch & 0x1f;
            if (tagNumber == 0x1f) { // Except that if it is 0x1F then the tag is encoded in the following bytes.
                tagNumber = 0;
                do {
                    ch = GetNextChar();
                    tagNumber = (tagNumber << 7) | (ch & 0x7f);
                } while ((ch & 0x80) != 0); // Top bit set means there's more to come.
            }

            // Next byte is the length.  If it is less than 128 it is the actual length, otherwise it
            // gives the number of bytes containing the length, except that if this is zero the item
            // has an "indefinite" length and is terminated by two zero bytes.
            ch = GetNextChar();
            if ((ch & 0x80) != 0) {
                int lengthOfLength = ch & 0x7f;
                if (lengthOfLength == 0) endOfItem = INDEFINITE_LENGTH;
                else {
                    endOfItem = 0;
                    while ((lengthOfLength--) != 0) {
                        ch = GetNextChar();
                        endOfItem = (endOfItem << 8) | ch;
                    }
                    endOfItem += Position;
                }
            }
            else endOfItem = ch + Position;

            if (tagClass == Context)
            {
                TaggedNode node = new TaggedNode(tagNumber);
                // The argument here depends on the particular tag we're processing.
                switch ((ASN1ContextCodes)tagNumber) {
                case ASN1ContextCodes.C_MULTIPLE_SELECTION:
                case ASN1ContextCodes.C_OBSCURED_INPUT:
                case ASN1ContextCodes.C_INITIALLY_AVAILABLE:
                case ASN1ContextCodes.C_WRAP_AROUND:
                case ASN1ContextCodes.C_TEXT_WRAPPING:
                case ASN1ContextCodes.C_INITIALLY_ACTIVE:
                case ASN1ContextCodes.C_MOVING_CURSOR:
                case ASN1ContextCodes.C_SHARED:
                case ASN1ContextCodes.C_ENGINE_RESP:
                case ASN1ContextCodes.C_TILING:
                case ASN1ContextCodes.C_BORDERED_BOUNDING_BOX:
                    { // BOOL
                        // If there is no argument we need to indicate that so that it gets
                        // the correct default value.
                        if (Position != endOfItem){
                            int intVal = ParseInt(endOfItem); // May raise an exception
                            node.Append(new BoolNode(intVal != 0));
                        }
                        break;
                    }

                case ASN1ContextCodes.C_INPUT_TYPE:
                case ASN1ContextCodes.C_SLIDER_STYLE:
                case ASN1ContextCodes.C_TERMINATION:
                case ASN1ContextCodes.C_ORIENTATION:
                case ASN1ContextCodes.C_HORIZONTAL_JUSTIFICATION:
                case ASN1ContextCodes.C_BUTTON_STYLE:
                case ASN1ContextCodes.C_START_CORNER:
                case ASN1ContextCodes.C_LINE_ORIENTATION:
                case ASN1ContextCodes.C_VERTICAL_JUSTIFICATION:
                case ASN1ContextCodes.C_STORAGE:
                    { // ENUM
                        if (Position != endOfItem){
                            int intVal = ParseInt(endOfItem); // May raise an exception
                            node.Append(new EnumNode(intVal));
                        }
                        break;
                    }

                case ASN1ContextCodes.C_INITIAL_PORTION:
                case ASN1ContextCodes.C_STEP_SIZE:
                case ASN1ContextCodes.C_INPUT_EVENT_REGISTER:
                case ASN1ContextCodes.C_INITIAL_VALUE:
                case ASN1ContextCodes.C_IP_CONTENT_HOOK:
                case ASN1ContextCodes.C_MAX_VALUE:
                case ASN1ContextCodes.C_MIN_VALUE:
                case ASN1ContextCodes.C_LINE_ART_CONTENT_HOOK:
                case ASN1ContextCodes.C_BITMAP_CONTENT_HOOK:
                case ASN1ContextCodes.C_TEXT_CONTENT_HOOK:
                case ASN1ContextCodes.C_STREAM_CONTENT_HOOK:
                case ASN1ContextCodes.C_MAX_LENGTH:
                case ASN1ContextCodes.C_CHARACTER_SET:
                case ASN1ContextCodes.C_ORIGINAL_TRANSPARENCY:
                case ASN1ContextCodes.C_ORIGINAL_GC_PRIORITY:
                case ASN1ContextCodes.C_LOOPING:
                case ASN1ContextCodes.C_ORIGINAL_LINE_STYLE:
                case ASN1ContextCodes.C_STANDARD_VERSION:
                case ASN1ContextCodes.C_ORIGINAL_LINE_WIDTH:
                case ASN1ContextCodes.C_CONTENT_HOOK:
                case ASN1ContextCodes.C_CONTENT_CACHE_PRIORITY:
                case ASN1ContextCodes.C_COMPONENT_TAG:
                case ASN1ContextCodes.C_ORIGINAL_VOLUME:
                case ASN1ContextCodes.C_PROGRAM_CONNECTION_TAG:
                case ASN1ContextCodes.C_CONTENT_SIZE:
                    { // INT
                        if (Position != endOfItem){
                            int intVal = ParseInt(endOfItem); // May raise an exception
                            node.Append(new IntNode(intVal));
                        }
                        break;
                    }

                case ASN1ContextCodes.C_OBJECT_INFORMATION:
                case ASN1ContextCodes.C_CONTENT_REFERENCE:
                case ASN1ContextCodes.C_FONT_ATTRIBUTES:
                case ASN1ContextCodes.C_CHAR_LIST:
                case ASN1ContextCodes.C_NAME:
                case ASN1ContextCodes.C_ORIGINAL_LABEL:
                    { // STRING
                        // Unlike INT, BOOL and ENUM we can't distinguish an empty string
                        // from a missing string.
                        node.Append(new OctetStringNode( ParseString(endOfItem)));
                        break;
                    }

                default:
                    {
                        // Everything else has either no argument or is self-describing
                        // TODO: Handle indefinite length.
                        //Logging.Assert(endOfItem != INDEFINITE_LENGTH); // For the moment.
                        while (Position < endOfItem) {
                            node.Append(DoParse());
                        }
                        break;
                    }
                }                
                return node;
            }
            else { // Universal - i.e. a primitive type.
                // Tag values

                switch ((ASN1UniversalCodes)tagNumber) 
                {
                    case ASN1UniversalCodes.U_BOOL: // Boolean
                    {
                        return new BoolNode( ParseInt(endOfItem) != 0);     
                    }
                    case ASN1UniversalCodes.U_INT: // Integer
                    {
                        return new IntNode(ParseInt(endOfItem));
                    }
                    case ASN1UniversalCodes.U_ENUM: // ENUM
                    {
                        return new EnumNode(ParseInt(endOfItem));
                    }
                    case ASN1UniversalCodes.U_STRING: // String
                    {
                        return new OctetStringNode(ParseString(endOfItem));
                    }
                    case ASN1UniversalCodes.U_NULL: // ASN1 NULL
                    {
                        return new NullNode();
                    }
                    case ASN1UniversalCodes.U_SEQUENCE: // Sequence
                    {
                        SequenceNode node = new SequenceNode();
                        //Logging.Assert(endOfItem != INDEFINITE_LENGTH); // TODO: Implement this.
                        while (Position < endOfItem) 
                        {
                            node.Append(DoParse());
                        }
                        //Logging.Assert(Position == endOfItem);
                        return node;
                    }
                default:
                    //Logging.Assert(false);
                    throw new OpenMhegException("Unknown universal");
                }
            }
        }

  

    }
}
